QQ 浏览器中的Hook、Root、模拟器、Debug、DexFile检测技术
在QQ浏览器的崩溃报告中,会发送大量当前设备的状态、设备的信息、硬件的信息、等等。其中就包含了Hook检测、Root检测、模拟器检测、DexFile检测、Debug检测这些信息。
由于这部分代码使用了名称混淆,所以我自己手工把代码还原了一遍,替换为有意义的名称。部分逻辑修改了一下,下面我们就依次来看。
说是Hook检测,不如说是对目前比较流行的框架xposed、substrate的检测。
检测Package
这部分代码比较简单,就是检测是否安装了xposed或者substrate。
public static int checkPackage(Context context) {
int i = 0;
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getInstallerPackageName("de.robv.android.xposed.installer");
i = 1;
} catch (Exception e) {
}
try {
packageManager.getInstallerPackageName("com.saurik.substrate");
return i | 2;
} catch (Exception e2) {
return i;
}
}
检测/proc/mypid/maps
这里我对Linux的proc文件系统没有研究过的童鞋非常简单的搜一下盲。
首先proc文件系统的设计目的之一就是允许更方便的对进程信息进行访问。
每当一个进程创建的时候,/proc目录下就会有和该进程id对应的目录产生。
目录名称就是进程id,里面记录该进程的各种信息,其中maps记录了进程的内存信息,更具体的说内存分段信息,具体什么含义我就不说了,反正我们可以知道它可以记录加载了那些模块就OK了。如下图:
对Xposed有研究过的人应该都知道,xposed会对进程注入这些模块:
XposedBridge.jar
libxposed_art.so
app_process32_xposed
所以我们检测这些模块是否存在也是可以检测出xposed和substrate的。
public static int checkMap() throws Throwable {
UnsupportedEncodingException unsupportedEncodingException;
BufferedReader bufferedReader;
Throwable th;
int i = 0;
BufferedReader bufferedReader2;
int i2;
int result = 0;
try {
HashSet hashSet = new HashSet();
bufferedReader2 = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/" + android.os.Process.myPid() + "/maps"), "utf-8"));
while (true) {
try {
String readLine = bufferedReader2.readLine();
if (readLine == null) {
break;
} else if (readLine.endsWith(".so") || readLine.endsWith(".jar")) {
hashSet.add(readLine.substring(readLine.lastIndexOf(" ") + 1));
}
} catch (UnsupportedEncodingException e) {
unsupportedEncodingException = e;
i2 = 0;
bufferedReader = bufferedReader2;
try {
unsupportedEncodingException.printStackTrace();
if (bufferedReader != null) {
}
} catch (Throwable th2) {
th = th2;
bufferedReader2 = bufferedReader;
if (bufferedReader2 != null) {
}
throw th;
}
}
}
Iterator it = hashSet.iterator();
while (it.hasNext()) {
int i3;
Object next = it.next();
if (((String) next).toLowerCase().contains("xposed")) {
result = result | 64;
}
if (((String) next).toLowerCase().contains("com.saurik.substrate")) {
result = result | 128;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
代码逻辑就是一行一行的读/proc/mypid/mpas,看是否包含xposed或com.saurik.substrate。
检测堆栈信息
先看图:
啊啊,我们只要检测堆栈是否包含红色圈圈de.robv.android.xposed.XposedBridge 就可以了。
看代码
public static int checkStackTraceElement() {
int i = 0;
try {
throw new Exception("detect hook");
} catch (Exception e) {
int i2 = 0;
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("main")) {
i2 |= 4;
}
if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals("handleHookedMethod")) {
i2 |= 8;
}
if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && stackTraceElement.getMethodName().equals("invoked")) {
i2 |= 16;
}
if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
i++;
if (i == 2) {
i2 |= 32;
}
}
}
return i2;
}
}
其实我们可以依据该方法检测自己的方法是否被Hook了,道理是一样的。就是检测堆栈是否包含有afterHookedMethod、beforeHookedMethod。
模拟器检测
模拟器都具有一些特殊的属性,查找这个特殊的属性就可以判断是否是模拟器。
其中AdbShell.getprop 等效于 Systemproperties.get(name)
检测Debug
一个是检测debuggable标志,一个是检测TracerPid。
Root检测
一个是检测是否有su文件,一个是检测属性。
private static final String[] suFiles = new String[]{"/su", "/su/bin/su", "/sbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/data/local/su", "/system/xbin/su", "/system/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/system/bin/cufsdosck", "/system/xbin/cufsdosck", "/system/bin/cufsmgr", "/system/xbin/cufsmgr", "/system/bin/cufaevdd", "/system/xbin/cufaevdd", "/system/bin/conbb", "/system/xbin/conbb"};
public static boolean haveSu()
{
boolean z = false;
boolean z2 = false;
for (String file : suFiles) {
if (new File(file).exists()) {
z = true;
break;
}
}
if (Build.TAGS == null || !Build.TAGS.contains("test-keys")) {
z2 = false;
} else {
z2 = true;
}
return z2 || z;
}
public static String RootCheckProp()
{
//ro.secure表示root权限,如果为0则表示启用root权限,1则相反
//这个只能检测ROM被刷入时的默认属性.
StringBuilder builder = new StringBuilder();
builder.append("ro.secure:");
builder.append(AdbShell.getprop("ro.secure"));
builder.append("\n");
builder.append("ro.adb.secure:");
builder.append(AdbShell.getprop("ro.adb.secure"));
builder.append("\n");
return builder.toString();
}
据说对一些比较难Root的手机厂商,修改rom里面的default.prop文件里的ro.secure为0,然后重签名再刷进去可以获得永久root。不过我没试过......比较懒。
DexFile文件检测
根据ClassLoad检测加载了那些Dex文件
//查询所有加载的dex,jar,apk文件,看一下是否有其他异己的模块加载
public static void allDex()
{
Object pathList = getDeclaredFieldValue(DexFileCheck.class.getClassLoader(),"pathList");
Object [] dexElements = (Object [])getDeclaredFieldValue(pathList,"dexElements");
for(Object dex:dexElements)
{
DexFile dexFile = (DexFile)getDeclaredFieldValue(dex,"dexFile");
if(dexFile == null)continue;
Log.d(TAG, "allDex: found dexfile "+ dexFile.getName());
}
}
其中这都是最简单最直接的检测方法,还有很多检测方法。但道理无非就一个,求同排异。
像不像人体的淋巴细胞,攻击者是病毒,而守护者就是淋巴系统。其实就是为了说明一个道理,学会类比。
- End -
看雪ID:chpeagle
https://bbs.pediy.com/user-793792.htm
本文由看雪论坛 chpeagle 原创
转载请注明来自看雪社区
⚠️ 注意
2019 看雪安全开发者峰会门票正在热售中!
长按识别下方二维码,即可享受 2.5折 优惠!
往期热门回顾
﹀
﹀
﹀
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com
↙点击下方“阅读原文”,查看更多干货